﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MacomberMapSystem.Common.Database;
using System.Security.Cryptography;
using MacomberMapSystem.Common.Internals;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using System.Data.Common;
using System.Data.SQLite;
using System.Data.OleDb;
using MacomberMapSystem.Common.Serialization;
using System.IO.Compression;

namespace MacomberMapSystem.Common.CIM.Direct
{
    /// <summary>
    /// This class provides direct access to a network model
    /// </summary>
    public class CIM_DirectModel : MM_Database_Model
    {
        #region Variable declarations

        ///<summary>The collection of all elements by type</summary>
        public MM_Indexed_Dictionary<CIM_RdfID, CIM_Element> Elements = new MM_Indexed_Dictionary<CIM_RdfID, CIM_Element>();

#if !NoCacheing
        /// <summary>Our temporary element collection to speed up creation of new elements</summary>
        public Stack<CIM_DirectElement> TempElements = new Stack<CIM_DirectElement>();
#endif

        /// <summary>The collection of CIM elements by TEID</summary>
        public Dictionary<UInt64, CIM_DirectElement> TEIDs = new Dictionary<ulong, CIM_DirectElement>();

        /// <summary>The collection of CIM elements by TEID</summary>
        public Dictionary<CIM_DirectElement, UInt64> ReverseTEIDs = new Dictionary<CIM_DirectElement, UInt64>();

        ///<summary>The list of attributes, and mapping to their allocations</summary>
        public MM_Indexed_Dictionary<String, int> _Attributes = new MM_Indexed_Dictionary<string, int>(255);

        /// <summary>The collection of all attributes by type</summary>
        public Dictionary<Byte, List<UInt16>> _AttributesByType = new Dictionary<byte, List<ushort>>(255);

        /// <summary>The collection of all types by attribute</summary>
        public Dictionary<ushort, List<byte>> _TypesByAttribute = new Dictionary<ushort, List<byte>>(255);

        /// <summary>Our flags of a chunk's load completion</summary>
        private bool[] CompletedPortions;

        /// <summary>The positions for each of the file chunks</summary>
        private long[] Positions;

        /// <summary>Our block size</summary>
        public long BlockSize = 8192;

        /// <summary>The size of our file</summary>
        public long FileSize;

        ///<summary>The collection of all elements by type</summary>
        public Dictionary<Byte, List<CIM_DirectElement>> ElementsByType = new Dictionary<byte, List<CIM_DirectElement>>(255);

        /// <summary>Our collection of type to types</summary>
        public Dictionary<Byte, List<byte>> TypeToTypes = new Dictionary<byte, List<byte>>();

        /// <summary>Our collection of type from types</summary>
        public Dictionary<Byte, List<byte>> TypeFromTypes = new Dictionary<byte, List<byte>>();
        #endregion

        #region Direct model opening
        /// <summary>
        /// Open our direct model into memory
        /// </summary>
        /// <param name="Repository"></param>
        /// <param name="cmbModel"></param>
        /// <param name="Model"></param>
        /// <param name="Models"></param>
        /// <returns></returns>
        public static CIM_DirectModel OpenModel(MM_Database_Model Model, MM_Repository Repository, Dictionary<int, MM_Database_Model> Models, ComboBox cmbModel)
        {
            String IndexPath = Path.Combine(Model.FullPath, Model.Name + ".CimIndex");
            CIM_DirectModel CurModel2 = new CIM_DirectModel(IndexPath, true, Repository);
            CurModel2.ID = Model.ID;
            Models.Remove(CurModel2.ID);
            Models.Add(CurModel2.ID, CurModel2);
            CurModel2.FullClass = Model.FullClass;
            CurModel2.ModelClass = Model.ModelClass;
            CurModel2.ModelCategory = Model.ModelCategory;
            CurModel2.PriorModel = Model.PriorModel;
            CurModel2.ValidStart = Model.ValidStart;
            CurModel2.ModelLocation = MM_Database_Model.enumModelLocations.InPrimary;
            CurModel2.Db = Model.Db;
            CurModel2.Repository = Model.Repository;
            if (cmbModel != null)
            {
                int OldIndex = cmbModel.Items.IndexOf(Model);
                cmbModel.Items.RemoveAt(OldIndex);
                cmbModel.Items.Insert(OldIndex, CurModel2);
                cmbModel.SelectedItem = CurModel2;
            }
            while (CurModel2.State != MM_Database_Model.enumModelState.Completed)
            {
                Application.DoEvents();
                Thread.Sleep(250);
            }
            return CurModel2;
        }


        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new direct model
        /// </summary>
        /// <param name="FileName">The file name of the model to be loaded</param>
        /// <param name="SingleThreaded">Whether to use single-threaded mode</param>
        /// <param name="Repository">The data repository</param>
        public CIM_DirectModel(String FileName, bool SingleThreaded, MM_Repository Repository)
        {
            this.DirectModel = true;
            FileSize = new FileInfo(FileName).Length;
            FullPath = Path.GetDirectoryName(FileName);
            ModelLocation = enumModelLocations.InPrimary;            
            Name = Path.GetFileNameWithoutExtension(FileName);
            this.Repository = Repository;
            this.StartTime = DateTime.Now;
            this.State = enumModelState.Opening_File;
            this.Current = 0;
            this.Total = FileSize;
            this.ExportedOn = new FileInfo(FileName).LastWriteTime;
            Positions = new long[1];
            CompletedPortions = new bool[] { false };


            //If we have an XML model, handle accordingly
            if (Path.GetExtension(FileName).Equals(".xml", StringComparison.CurrentCultureIgnoreCase))
                if (SingleThreaded || MessageBox.Show("Run in single-threaded mode?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                    ThreadPool.QueueUserWorkItem(new WaitCallback(CIMReaderUsingXml), new long[] { 0, new FileInfo(FileName).Length, 0 });
                else
                {
                    //Split up our file into appropriately sized chunks, and load it in.
                    int NumChunks = (int)Math.Ceiling((Decimal)FileSize / (Decimal)BlockSize);
                    CompletedPortions = new bool[NumChunks];
                    Positions = new long[NumChunks];
                    for (int a = 0; a < NumChunks; a++)
                    {
                        CompletedPortions[a] = false;
                        Positions[a] = 0;
                        ThreadPool.QueueUserWorkItem(new WaitCallback(CIMReaderUsingXml), new long[] { BlockSize * a, (BlockSize * (a + 1) - 1), a });
                    }
                }
            else if (Path.GetExtension(FileName).Equals(".cimIndex", StringComparison.CurrentCultureIgnoreCase))
                ThreadPool.QueueUserWorkItem(CIMReaderUsingOldIndex);
            ThreadPool.QueueUserWorkItem(new WaitCallback(WaitForCompletion), Path.GetExtension(FileName));
        }

      

        /// <summary>
        /// Wait until the process is completed
        /// </summary>
        /// <param name="state"></param>
        private void WaitForCompletion(object state)
        {
            bool NeedsRefresh;
            Total = (float)FileSize;
            do
            {
                NeedsRefresh = false;
                foreach (bool IsCompleted in CompletedPortions)
                    if (!IsCompleted)
                        NeedsRefresh = true;
                long Position = 0;
                foreach (long Pos in Positions)
                    Position += Pos;
                Current = (float)Position;
                Thread.Sleep(1000);
            } while (NeedsRefresh);

            foreach (CIM_DirectElement Elem in Elements.Values)
                Elem._InLinks.TrimExcess();
            BuildElementsByType();            
            BuildTypeToTypes();

            //Write out our attributes
            Attributes.Clear();
            foreach (String attr in _Attributes.Keys)
                Attributes.Add(attr);

            TypesByAttribute = new List<byte[]>(_TypesByAttribute.Count);
            foreach (List<byte> bytes in _TypesByAttribute.Values)
                TypesByAttribute.Add(bytes.ToArray());



            //Update our attributes by type
            AttributesByType = new List<ushort[]>();
            for (int Type = 0; Type < _AttributesByType.Count; Type++)
                AttributesByType.Add(_AttributesByType[(byte)Type].ToArray());

            this.ElementCount = this.Elements.Count;
            LoadCompanies();
            LoadSubstationsFromCIM();
            LoadLinesFromCIM();

            //Kick off the SQLite and .CIMIndex file writing
            if (!File.Exists(Path.Combine(ModelFullPath, Name + ".CimIndex")))
            {
                this.State = enumModelState.ExportingIndex;
                WriteOldIndex();
            }

            if (!File.Exists(Path.Combine(ModelFullPath, Name + ".SQLite")))
            {
                this.State = enumModelState.ExportingIndex;
                WriteIndex(enumDatabaseMode.SQLite);
            }

            LoadTraces();
            LoadPUNs();
            this.State = enumModelState.Completed;
        }


        /// <summary>
        /// Build a list of all elements by their types
        /// </summary>
        public void BuildElementsByType()
        {
            ElementsByType = new Dictionary<byte, List<CIM_DirectElement>>(Types.Count);
            for (byte Type = 0; Type <= (byte)Types.Count; Type++)
                ElementsByType.Add(Type, new List<CIM_DirectElement>());
            foreach (KeyValuePair<CIM_RdfID, CIM_Element> Elem in Elements)
                ElementsByType[Elem.Value.Type].Add(Elem.Value as CIM_DirectElement);
        }

        /// <summary>
        /// Build the list of type to type mappings
        /// </summary>
        public void BuildTypeToTypes()
        {
            this.State = enumModelState.Loading_Attributes_And_Type_Linkages;
            TypeToTypes.Clear();
            TypeFromTypes.Clear();
            for (byte Type = 0; Type < (byte)Types.Count; Type++)
            {
                
                //Build our list of outgoing types
                Dictionary<Byte, bool> OutType = new Dictionary<byte, bool>();
                foreach (CIM_DirectElement OutElem in ElementsByType[Type])
                    foreach (CIM_Link OutLink in OutElem.OutLinks)
                        if (!OutType.ContainsKey(OutLink.TypeByte))
                        {
                            OutType.Add(OutLink.TypeByte, true);
                            List<Byte> InLinks;
                            if (!TypeFromTypes.TryGetValue(OutLink.TypeByte, out InLinks))
                                TypeFromTypes.Add(OutLink.TypeByte, InLinks = new List<byte>());
                            if (!InLinks.Contains(Type))
                                InLinks.Add(Type);
                        }
                TypeToTypes.Add(Type, new List<Byte>(OutType.Keys));
            }

            foreach (List<byte> kvp in TypeToTypes.Values)
                kvp.TrimExcess();
            foreach (List<byte> kvp in TypeFromTypes.Values)
                kvp.TrimExcess();
        }
        #endregion

        #region Processing the raw text file
        /// <summary>
        /// Use an XmlTextReader to parse the file.
        /// </summary>
        /// <param name="State">Our state to be passed</param>
        private void CIMReaderUsingXml(object State)
        {
            long[] inObj = (long[])State;
            long StartPosition = inObj[0];
            long EndPosition = inObj[1];
            long CurChunk = inObj[2];

            //Pull out our variables
            //Load our parameters for this chunk to be processed, and open the file
            int NumElements = 0;

            //Open our reader            
            FileStream XmlStream = new FileStream(CIMFileName, FileMode.Open, FileAccess.Read, FileShare.Read);
            XmlStream.Position = StartPosition;

            //Now, keep searching through the file until we've found our first rdf:ID
            byte[] inByte = new byte[BlockSize];

            long StartElementPosition, EndElementPosition;
            int StartElement, EndElement;
            String ElemType, inLine;
            int rdfLocation, NumBytes;

            //Add in our "unknown" element
            Types.Add("UNKNOWN");
            _AttributesByType.Add(0, new List<ushort>(1));

            while (XmlStream.Position < EndPosition)
            {
                //Read a chunk in
                NumBytes = XmlStream.Read(inByte, 0, inByte.Length);
                inLine = Encoding.ASCII.GetString(inByte, 0, NumBytes);

                //If we have our rdf:ID tag, dial back to the beginning of the element, and process accordingly
                if ((rdfLocation = inLine.IndexOf("rdf:ID")) != -1)
                {
                    StartElement = inLine.LastIndexOf('<', rdfLocation);
                    StartElementPosition = XmlStream.Position + StartElement - NumBytes;
                    ElemType = inLine.Substring(StartElement + 1, rdfLocation - StartElement - 1).Trim();

                    //Now, locate the end of the element
                    while ((EndElement = inLine.IndexOf("</" + ElemType + ">", StartElement)) == -1)
                    {
                        //Dial back to the last element close, and read the next chunk
                        XmlStream.Seek(inLine.LastIndexOf('>') - NumBytes, SeekOrigin.Current);
                        NumBytes = XmlStream.Read(inByte, 0, inByte.Length);
                        inLine = Encoding.ASCII.GetString(inByte, 0, NumBytes);
                        StartElement = 0;
                    }

                    //Determine our end position, and process our element
                    EndElementPosition = (XmlStream.Position - NumBytes) + ElemType.Length + 3 + EndElement;
                    int ElemLength = (int)(EndElementPosition - StartElementPosition);

                    ProcessElement(XmlStream, StartElementPosition, ElemLength, ElemType);
                    NumElements++;
                    Positions[CurChunk] = XmlStream.Position - StartPosition;
                }
            }
            Positions[CurChunk] = EndPosition - StartPosition;
            CompletedPortions[CurChunk] = true;
        }
        #endregion

        #region Processing the old index
        /// <summary>
        /// Open a CIM file using the old index
        /// </summary>
        /// <param name="state"></param>
        private void CIMReaderUsingOldIndex(object state)
        {
            String File = Path.Combine(ModelFullPath, Path.GetFileNameWithoutExtension(Name) + ".CimIndex");            

            //Now, open up our index, and read our file
            this.State = enumModelState.Opening_File;
            this.Current = 0;
            this.Total = new FileInfo(File).Length;
            String InLine;

            using (FileStream fS = new FileStream(File, FileMode.Open, FileAccess.Read))
            {
                List<byte> inHeader = new List<byte>();
                byte inByte;
                while ((inByte = (byte)fS.ReadByte()) != 10)
                    inHeader.Add(inByte);
                String VersionInfo = Encoding.UTF8.GetString(inHeader.ToArray());

                using (GZipStream gS = new GZipStream(fS, CompressionMode.Decompress))
                using (StreamReader sRd = new StreamReader(gS, Encoding.UTF8))
                    while (!String.IsNullOrEmpty(InLine = sRd.ReadLine()))
                    {
                        Console.WriteLine("Got line " + InLine);
                        String Descriptor = InLine.Split(':')[0];                       
                        int Count = Convert.ToInt32(InLine.Split(':')[1]);
                        try
                        {
                            ProcessLine(Descriptor, Count, sRd);
                            System.Diagnostics.Debug.WriteLine("Finished processing " + InLine);
                            this.Current = fS.Position;
                        }
                        catch (Exception ex)
                        {
                            System.Diagnostics.Debug.WriteLine("Error processing " + InLine + ": " + MM_System_Interface.ConvertError(ex));                            
                        }
                        
                    }
            }
            CompletedPortions[0] = true;
        }

        /// <summary>
        /// Process input lines from the files
        /// </summary>
        /// <param name="Title"></param>
        /// <param name="Count"></param>
        /// <param name="sRd"></param>
        private void ProcessLine(String Title, int Count, StreamReader sRd)
        {
            if (Title.Equals("Maximum mRID", StringComparison.CurrentCultureIgnoreCase))
                MaxMRID = Count;
            else if (Title.Equals("Attributes", StringComparison.CurrentCultureIgnoreCase))
                for (int a = 0; a < Count; a++)
                {
                    String Attr = sRd.ReadLine();
                    _Attributes.Add(Attr, a);
                    _TypesByAttribute.Add((ushort)a, new List<byte>());
                }
            else if (Title.Equals("Types", StringComparison.CurrentCultureIgnoreCase))
                for (int a = 0; a < Count; a++)
                {
                    String[] splLine = sRd.ReadLine().Split(',');
                    List<ushort> OutLst = new List<ushort>(5);
                    for (int b = 1; b < splLine.Length; b++)
                        if (!String.IsNullOrEmpty(splLine[b]))
                        {
                            ushort AttributeNumber = ushort.Parse(splLine[b]);
                            OutLst.Add(AttributeNumber);
                            if (!_TypesByAttribute.ContainsKey((ushort)a))
                                _TypesByAttribute.Add(AttributeNumber, new List<byte>(20));
                            if (!_TypesByAttribute[AttributeNumber].Contains((byte)a))
                                _TypesByAttribute[AttributeNumber].Add((byte)a);

                        }
                    Types.Add(splLine[0]);
                    _AttributesByType.Add((byte)(Types.Count - 1), OutLst);
                }
           
            else if (Title.Equals("TypesByAttribute",StringComparison.CurrentCultureIgnoreCase))
                for (int a = 0; a < Count; a++)
                {
                    String[] splLine = sRd.ReadLine().Split(',');
                    List<byte> InBytes = new List<byte>();
                    for (int b = 1; b < splLine.Length; b++)
                        if (!String.IsNullOrEmpty(splLine[b]))
                        InBytes.Add(Convert.ToByte(splLine[b]));
                    if (TypesByAttribute == null)
                        TypesByAttribute = new List<byte[]>(Count);
                    TypesByAttribute.Add(InBytes.ToArray());                    
                }
                    
            else if (Title.Equals("TypeToTypes", StringComparison.CurrentCultureIgnoreCase) || Title.Equals("TypeFromTypes", StringComparison.CurrentCultureIgnoreCase))
                for (int a = 0; a < Count; a++)
                {
                    String inLine = sRd.ReadLine();
                    String[] splLine = inLine.Split(',');
                    List<byte> OutLst = new List<byte>();
                    for (int b = 1; b < splLine.Length; b++)
                        if (splLine[b] != "")
                            OutLst.Add(byte.Parse(splLine[b]));
                    if (Title.Equals("TypeToTypes", StringComparison.CurrentCultureIgnoreCase))
                        TypeToTypes.Add(byte.Parse(splLine[0]), OutLst);
                    else
                        TypeFromTypes.Add(byte.Parse(splLine[0]), OutLst);
                }
            else if (Title.Equals("Elements", StringComparison.CurrentCultureIgnoreCase))
                for (int a = 0; a < Count; a++)
                {
                    String[] splLine = sRd.ReadLine().Split(',');
                    CIM_DirectElement NewElem = AddCIMElement(splLine[0], long.Parse(splLine[2]), int.Parse(splLine[3]), splLine[1], "");
                    foreach (String inLink in splLine[4].Split('|'))
                        if (inLink != "")
                            NewElem._InLinks.Add(int.Parse(inLink));
                    foreach (String outLink in splLine[5].Split('|'))
                        if (outLink != "")
                            NewElem._OutLinks.Add(int.Parse(outLink));
                    NewElem._InLinks.TrimExcess();
                    NewElem._OutLinks.TrimExcess();
                    if (splLine.Length == 7)
                        NewElem.CheckSum = Convert.FromBase64String(splLine[6]);
                    Current = a;
                    Total = Count;
                }
            else if (Title.Equals("TEIDs", StringComparison.CurrentCultureIgnoreCase))
            {
                TEIDs = new Dictionary<ulong, CIM_DirectElement>(Count);
                TEIDCount = Count;
                ReverseTEIDs = new Dictionary<CIM_DirectElement, ulong>(Count);
                for (int a = 0; a < Count; a++)
                {                    
                    String[] splLine = sRd.ReadLine().Split(',');
                    UInt64 TEID = UInt64.Parse(splLine[0]);
                    TEIDs.Add(TEID, Elements[new CIM_RdfID(splLine[1])] as CIM_DirectElement);
                    ReverseTEIDs.Add(Elements[new CIM_RdfID(splLine[1])] as CIM_DirectElement, TEID);
                    MaxTEID = Math.Max(MaxTEID, TEID);
                }
            }
        }
        #endregion

        #region Model addition/location
        /// <summary>
        /// Return an element from its ID
        /// </summary>
        /// <param name="ID"></param>
        /// <param name="Repository"></param>
        /// <returns></returns>
        public override CIM_Element FromID(int ID, MM_Repository Repository)
        {
            return TestCache(Elements[ID]);
        }

        /// <summary>
        /// Return an element's rdf:ID
        /// </summary>
        /// <param name="TargetID"></param>
        /// <returns></returns>
        public override CIM_RdfID rdfIDFromID(int TargetID)
        {            
            return Elements[ID].rdfID;
        }

        /// <summary>
        /// Get an element by rdf:ID
        /// </summary>
        /// <param name="RdfID"></param>
        /// <returns></returns>
        public override CIM_Element FromRdfID(string RdfID)
        {
            return TestCache(Elements[new CIM_RdfID(RdfID)]);
        }

        /// <summary>
        /// Get an element by rdf;ID
        /// </summary>
        /// <param name="RdfID"></param>
        /// <returns></returns>
        public override CIM_Element FromRdfID(CIM_RdfID RdfID)
        {
            return TestCache(Elements[RdfID]);
        }

        /// <summary>
        /// Get an element by TEID
        /// </summary>
        /// <param name="TEID"></param>
        /// <returns></returns>
        public override CIM_Element FromTEID(ulong TEID)
        {
            return TestCache(TEIDs[TEID]);
        }   

        /// <summary>
        /// Return the list of elements of a certain type
        /// </summary>
        /// <param name="ElemType"></param>
        /// <returns></returns>
        public override CIM_Element[] ElementsOfType(string ElemType)
        {
            //Build our list of target types            
            List<int> TargetTypes = new List<int>();
            for (int a = 0; a < Types.Count; a++)
                if (Types[a].EndsWith(ElemType, StringComparison.CurrentCultureIgnoreCase))
                    TargetTypes.Add(a);

            List<CIM_Element> OutElems = new List<CIM_Element>();
            CIM_Element_Difference cDiff;
            foreach (int TargetType in TargetTypes)
            {
                //First, pull in our existing elements
                foreach (CIM_Element Elem in ElementsByType[(byte)TargetType].ToArray())
                    if (!ModelChanges.TryGetValue(Elem.rdfID, out cDiff) || (cDiff.ChangeType & CIM_Element_Difference.enumChangeType.ElementDeletion) != CIM_Element_Difference.enumChangeType.ElementDeletion)                        
                    OutElems.Add(Elem);
                foreach (CIM_Element_Difference ElemDiff in ModelChanges.Values)
                    if (ElemDiff.Type == TargetType && (ElemDiff.ChangeType & CIM_Element_Difference.enumChangeType.ElementAddition) == CIM_Element_Difference.enumChangeType.ElementAddition)
                        OutElems.Add(new CIM_FakeElement(ElemDiff.TypeName, ElemDiff.rdfID, ElemDiff.AddedAttributes, ElemDiff.AddedLinks.ToArray(), this));                           
            }
            return TestCache(OutElems.ToArray());
        }


        /// <summary>
        /// Process an XML element
        /// </summary>
        /// <param name="XmlStream">The XML File Stream</param>
        /// <param name="Start">The starting position of the XML</param>
        /// <param name="Length">The length of the XML</param>
        /// <param name="ElementType">The element type of the XML</param>
        private void ProcessElement(FileStream XmlStream, long Start, int Length, String ElementType)
        {
            XmlStream.Position = Start;
            Byte[] inBytes = new byte[Length];
            int NumBytes = XmlStream.Read(inBytes, 0, Length);
            String inLine = Encoding.ASCII.GetString(inBytes, 0, NumBytes);

            //Determine our full rdf:ID
            int rdfLocation = inLine.IndexOf("rdf:ID");
            int rdfIDEnd = inLine.IndexOf('\"', rdfLocation + 8);
            String rdfID = inLine.Substring(rdfLocation + 8, rdfIDEnd - rdfLocation - 8);
            AddCIMElement(rdfID, Start, Length, ElementType, inLine)._OutLinks.TrimExcess();
        }


        /// <summary>
        /// Add a CIM element to the repository
        /// </summary>
        /// <param name="rdfID">The rdf:ID of the element</param>
        /// <param name="ElemStart">The starting position of the element</param>
        /// <param name="ElemLength">The length of the element</param>
        /// <param name="ElementXml">The XML of the element to be processed</param>
        /// <param name="ElementType">The type of element to be processed</param>
        /// <returns>The newly-created CIM element</returns>
        public CIM_DirectElement AddCIMElement(string rdfID, long ElemStart, int ElemLength, String ElementType, String ElementXml)
        {
            //First, convert our rdf:ID to our optimized-for-storage ID.
            CIM_RdfID CIMrdfID = new CIM_RdfID(rdfID);
            //TODO: Log malformed rdf:IDs
            //            if (CIMrdfID.ToString() != rdfID)
            //              MM_Log.LogEvent(new MM_Event_IDMismatch(rdfID, CIMrdfID.ToString()));


            //Locate our element, and set its parameters
            CIM_DirectElement NewElement = LocateElement(CIMrdfID);
            NewElement.Start = ElemStart;
            NewElement.Length = ElemLength;
            NewElement.rdfID = CIMrdfID;
            NewElement.TypeName = ElementType;


            //Now, manage our attributes and links
            if (!String.IsNullOrEmpty(ElementXml))
            {
                ManageAttributesAndLinks(NewElement, ElementXml);
                NewElement.CheckSum = BuildSortedChecksum(ElementXml);
            }
            return (CIM_DirectElement)TestCache(NewElement);
        }

        /// <summary>
        /// Build a sorted checksum of our element
        /// </summary>
        /// <param name="InnerXml"></param>
        private byte[] BuildSortedChecksum(String InnerXml)
        {
            //First, build our dictionary of elements
            List<KeyValuePair<String, String>> InValues = new List<KeyValuePair<string, string>>();
            if (InnerXml.Length > 0)
                InValues.Add(new KeyValuePair<String, String>(" Type", InnerXml.Substring(1, InnerXml.IndexOf(' ') - 1)));
            else
                InValues.Add(new KeyValuePair<String, String>(" Type", "UNKNOWN"));
            int CurPos = 1;
            if (InnerXml.Length > 0)
                while ((CurPos = InnerXml.IndexOf('<', CurPos) + 1) != 0)
                {
                    String AttrName = InnerXml.Substring(CurPos, InnerXml.IndexOf('>', CurPos) - CurPos);
                    String Val = null;
                    if (AttrName.Contains(" "))
                    {
                        Val = AttrName.Substring(AttrName.IndexOf('=') + 2);
                        Val = Val.Substring(0, Val.Length - 2);
                        AttrName = AttrName.Substring(0, AttrName.IndexOf(' '));
                    }
                    else if (AttrName[0] != '/')
                    {
                        Val = InnerXml.Substring(InnerXml.IndexOf('>', CurPos) + 1);
                        Val = Val.Substring(0, Val.IndexOf('<'));
                    }
                    InValues.Add(new KeyValuePair<String,String>(AttrName, Val));
                }
            InValues.Sort(new KvpComparer());
            StringBuilder sB = new StringBuilder();
            foreach (KeyValuePair<String, String> kvp in InValues)
                sB.AppendLine(kvp.Key + "=" + kvp.Value);
            return SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(sB.ToString()));
        }

        /// <summary>
        /// This class provides a comparison point for our elements
        /// </summary>
        private class KvpComparer: IComparer<KeyValuePair<String,String>>
        {
            /// <summary>
            /// Compare two points
            /// </summary>
            /// <param name="x"></param>
            /// <param name="y"></param>
            /// <returns></returns>
            public int Compare(KeyValuePair<string, string> x, KeyValuePair<string, string> y)
            {
                int CompareTitle = string.Compare(x.Key, y.Key, false);
                if (CompareTitle != 0)
                    return CompareTitle;
                else
                    return String.Compare(x.Value, y.Value, false);                
            }            
        }

        /// <summary>
        /// Retrieve a temporary element from the stack, and replenish if necessary
        /// </summary>
        /// <returns></returns>
        public CIM_DirectElement GetTempElement()
        {
#if NoCacheing
            return new CIM_DirectElement(this);
#else
            //Check to see whether temporary elements need to be added            
            if (TempElements.Count == 0)
                for (int a = 0; a < 100000 / 10; a++)
                    TempElements.Push(new CIM_DirectElement(this));
            return TempElements.Pop();
#endif
        }


        /// <summary>
        /// Locate a CIM element by its rdf:ID. If one cannot be found, create a stub
        /// </summary>
        /// <param name="rdfID">The rdf:ID to search for.</param>
        /// <returns></returns>
        public CIM_DirectElement LocateElement(CIM_RdfID rdfID)
        {
            CIM_DirectElement OutElement;
            CIM_Element FoundElem;
            //If the element isn't already in the database, pull it in
            if (!Elements.TryGetValue(rdfID, out FoundElem) || FoundElem is CIM_DirectElement == false)
            {
                while ((OutElement = GetTempElement()) == null)
                    System.Threading.Thread.Sleep(0);
                OutElement.rdfID = rdfID;
                OutElement.TypeName = "UNKNOWN";
                OutElement.Start = -1;
                OutElement.Length = -1;

                //Add our element to the collections
                Elements.Add(rdfID, OutElement);
            }
            else
                OutElement = FoundElem as CIM_DirectElement;
            return OutElement;
        }

        /// <summary>
        /// Go through the XML for an element, and update its links and attribute collections
        /// </summary>
        /// <param name="NewElement">The newly created CIM element</param>
        /// <param name="ElementXML">The XML of our element</param>
        public void ManageAttributesAndLinks(CIM_DirectElement NewElement, String ElementXML)
        {
            int CurPos = 1;
            //Go through all elements within the XML. If we have data within (not </endelement), add it to our information.
            while ((CurPos = ElementXML.IndexOf("<", CurPos) + 1) != 0)
                if (ElementXML[CurPos] != '/')
                {
                    int EndAttributeName = ElementXML.IndexOfAny(new char[] { '/', '>', ' ' }, CurPos);
                    String AttributeName = ElementXML.Substring(CurPos, EndAttributeName - CurPos);

                    int QuoteStart = ElementXML.IndexOf('\"', EndAttributeName);
                    String AttributeValue;
                    if (ElementXML[EndAttributeName + 1] == '/' || ElementXML[EndAttributeName + 2] == '/')
                        AttributeValue = "";
                    else if (QuoteStart != -1 && QuoteStart < ElementXML.IndexOfAny(new char[] { '/', '>' }, EndAttributeName))
                        AttributeValue = ElementXML.Substring(QuoteStart + 1, ElementXML.IndexOf('\"', QuoteStart + 1) - QuoteStart - 1);
                    else
                        AttributeValue = ElementXML.Substring(EndAttributeName + 1, ElementXML.IndexOf('<', EndAttributeName) - EndAttributeName - 1);
                    ProcessAttribute(NewElement, AttributeName, AttributeValue);
                }
        }

        /// <summary>
        /// Process an attribute 
        /// </summary>
        /// <param name="InElement">The element being processed</param>
        /// <param name="AttributeName">The name of the attribute</param>
        /// <param name="AttributeValue">The value of the attribute</param>
        public void ProcessAttribute(CIM_DirectElement InElement, string AttributeName, string AttributeValue)
        {
            //First, check to see whether our attribute is stored
            ushort AttributeNumber = (ushort)_Attributes.Lookup(AttributeName);

            //Now, check to see if our type to type to attribute mapping is in place
            lock (_AttributesByType)
            {
                if (!_AttributesByType.ContainsKey(InElement.Type))
                    _AttributesByType.Add(InElement.Type, new List<ushort>(20));
                if (!_AttributesByType[InElement.Type].Contains(AttributeNumber))
                    _AttributesByType[InElement.Type].Add(AttributeNumber);
            }

            //Also, check to see if our attribute to type mapping is in place
            lock (_TypesByAttribute)
            {
                if (!_TypesByAttribute.ContainsKey(AttributeNumber))
                    _TypesByAttribute.Add(AttributeNumber, new List<byte>(20));
                if (!_TypesByAttribute[AttributeNumber].Contains(InElement.Type))
                    _TypesByAttribute[AttributeNumber].Add(InElement.Type);
            }

            //Now, check to see if our attribute value is a link, and if so, handle accordingly.
            if (AttributeValue.Length > 0 && AttributeValue[0] == '#')
            {
                CIM_DirectElement Target = LocateElement(new CIM_RdfID(AttributeValue.Substring(1)));
                InElement._OutLinks.Add(Target.ID);
                Target._InLinks.Add(InElement.ID);
            }

            //Also, check to see if the attribute is a TEID. If so, add it in.
            if (AttributeName.EndsWith(".teid", StringComparison.CurrentCultureIgnoreCase) && AttributeValue.Length > 0)
            {
                UInt64 TEID = Convert.ToUInt64(AttributeValue);
                lock (TEIDs)
                    if (TEIDs.ContainsKey(TEID))
                    { } //TODO: Handle duplicate TEIDs  MM_Log.LogEvent(new MM_Event_DuplicateValue(InElement, TEIDs[TEID], "TEID"));
                    else
                    {
                        TEIDs.Add(TEID, InElement);
                        MaxTEID = Math.Max(TEID, MaxTEID);
                        ReverseTEIDs.Add(InElement, TEID);
                    }
            }
            int CurMRID;
            if (AttributeName.EndsWith(".mrid", StringComparison.CurrentCultureIgnoreCase) && AttributeValue.Length > 0 && Int32.TryParse(AttributeValue, out CurMRID))
                MaxMRID = Math.Max(MaxMRID, CurMRID);
        }
        #endregion

        #region Old Index Writing
        /// <summary>
        /// Write out the old index for our element
        /// </summary>
        private void WriteOldIndex()
        {
            this.State = enumModelState.ExportingIndex;
            Total = Attributes.Count + Types.Count + TypeToTypes.Count + TypeFromTypes.Count + Elements.Count + TEIDs.Count + TypesByAttribute.Count;
            Current = 0;
            using (FileStream fS = new FileStream(Path.Combine(ModelFullPath, Name + ".CimIndex"), FileMode.Create))
            {
                byte[] OutHeader = Encoding.UTF8.GetBytes(" Macomber Map Server System v" + Application.ProductVersion + ", (C) 2009-2011 ERCOT, Michael E. Legatt, Ph.D.\n");
                fS.Write(OutHeader, 0, OutHeader.Length);
                using (GZipStream gS = new GZipStream(fS, CompressionMode.Compress))
                using (StreamWriter sW = new StreamWriter(gS, Encoding.UTF8))
                {
                    //Write our maximum mRID
                    sW.WriteLine("Maximum mRID:" + MaxMRID.ToString());


                    //Write our attributes out
                    sW.WriteLine("Attributes:{0}", Attributes.Count);
                    foreach (String Attr in _Attributes.Keys)
                    {
                        sW.WriteLine(Attr);
                        Current++;
                    }

                    //Write our types out
                    sW.WriteLine("Types:{0}", Types.Count);
                    for (int a = 0; a < Types.Count; a++)
                    {
                        sW.Write(Types[a] + ",");
                        foreach (int AttrOut in AttributesByType[a])
                            sW.Write(AttrOut.ToString() + ",");
                        sW.WriteLine();
                        Current++;
                    }


                    sW.WriteLine("TypesByAttribute:{0}", TypesByAttribute.Count);
                    for (int a = 0; a < TypesByAttribute.Count; a++)
                    {
                        sW.Write(a.ToString() + ",");
                        foreach (byte TypeOut in TypesByAttribute[a])
                            sW.Write(TypeOut.ToString() + ",");
                        sW.WriteLine();
                        Current++;
                    }


                    //Write our type-to-type mappings
                    sW.WriteLine("TypeToTypes:{0}", TypeToTypes.Count);
                    foreach (KeyValuePair<byte, List<byte>> kvp in TypeToTypes)
                    {
                        sW.Write(kvp.Key.ToString());
                        foreach (byte kvp2 in kvp.Value)
                            sW.Write("," + kvp2.ToString());
                        sW.WriteLine();
                        Current++;
                    }

                    sW.WriteLine("TypeFromTypes:{0}", TypeFromTypes.Count);
                    foreach (KeyValuePair<byte, List<byte>> kvp in TypeFromTypes)
                    {
                        sW.Write(kvp.Key.ToString());
                        foreach (byte kvp2 in kvp.Value)
                            sW.Write("," + kvp2.ToString());
                        sW.WriteLine();
                        Current++;
                    }


                    //Write our elements
                    sW.WriteLine("Elements:{0}", Elements.Count);
                    foreach (CIM_DirectElement Elem in Elements.Values)
                    {
                        sW.Write("{0},{1},{2},{3},", Elem.rdfID.ToString(), Elem.TypeName, Elem.Start, Elem.Length);
                        for (int a = 0; a < Elem._InLinks.Count; a++)
                            sW.Write(a == 0 ? Elem._InLinks[a].ToString() : "|" + Elem._InLinks[a].ToString());
                        sW.Write(",");
                        for (int a = 0; a < Elem._OutLinks.Count; a++)
                            sW.Write(a == 0 ? Elem._OutLinks[a].ToString() : "|" + Elem._OutLinks[a].ToString());
                        sW.Write(",");
                        if (Elem.CheckSum != null)
                            sW.Write(Convert.ToBase64String(Elem.CheckSum));
                        sW.WriteLine();
                        Current++;
                    }

                    //Write our TEIDs
                    sW.WriteLine("TEIDs:{0}", TEIDs.Count);
                    foreach (KeyValuePair<UInt64, CIM_DirectElement> kvp in TEIDs)
                        sW.WriteLine("{0},{1}", kvp.Key, kvp.Value.rdfID);
                    Current++;
                }
            }
            State = enumModelState.Completed;
        }
        #endregion

        #region New Index writing
        /// <summary>The format for the outgoing database</summary>
        public enum enumDatabaseMode
        {
            /// <summary>SQLite database format</summary>
            SQLite,
            /// <summary>Microsft database (MDB) format</summary>
            MicrosoftDb,
            /// <summary>The old CimIndex format</summary>
            OldIndex
        }

        /// <summary>
        /// Write out the index for the current direct model
        /// </summary>
        /// <param name="DbMode"></param>
        public void WriteIndex(enumDatabaseMode DbMode)
        {
            WriteIndex(DbMode, null);
        }

        /// <summary>
        /// Write out our index file
        /// </summary>
        /// <param name="DbMode"></param>        
        /// <param name="ModelToInsert">The model to insert, if any</param>
        public void WriteIndex(enumDatabaseMode DbMode, MM_Database_Model ModelToInsert)
        {
            if (ModelToInsert == null)
                ModelToInsert = this;
            
            State = enumModelState.ExportingIndex;
            Current = 0;
            
            if (DbMode == enumDatabaseMode.OldIndex)
                WriteOldIndex();
            else
            {                
                float Total = ModelToInsert.Attributes.Count + ModelToInsert.Types.Count  + ModelToInsert.TypesByAttribute.Count + ModelToInsert.AttributesByType.Count;
                if (ModelToInsert is CIM_DirectModel)
                {
                    CIM_DirectModel DM = ModelToInsert as CIM_DirectModel;
                    Total += DM.TypeToTypes.Count + DM.TypeFromTypes.Count + DM.Elements.Count + DM.TEIDs.Count;
                }                
                String DbName = Path.Combine(ModelFullPath, Name + (DbMode == enumDatabaseMode.SQLite ? ".SQLite" : ".mdb"));
                if (File.Exists(DbName))
                    File.Delete(DbName);

                //Now, build our database connector as appropriate
                using (DbConnection dConn = (DbMode == enumDatabaseMode.SQLite ? new SQLiteConnection("Data Source=" + DbName) as DbConnection : new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + DbName + ";Jet OLEDB:Engine Type=5;") as DbConnection))
                {
                    dConn.Open();
                    CreateTables(dConn);

                    using (DbTransaction dTrans = dConn.BeginTransaction())
                    {
                        ExecuteCommand(dConn, dTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", ref Current, "TEIDCount", ModelToInsert.TEIDCount = TEIDs.Count);
                        ExecuteCommand(dConn, dTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", ref Current, "ElementCount", ModelToInsert.ElementCount);
                        ExecuteCommand(dConn, dTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", ref Current, "MaxMRID", ModelToInsert.MaxMRID);
                        ExecuteCommand(dConn, dTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", ref Current, "MaxTEID", ModelToInsert.MaxTEID);
                        ExecuteCommand(dConn, dTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", ref Current, "ExportedOn", ModelToInsert.ExportedOn);
                        ExecuteCommand(dConn, dTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", ref Current, "ModelClass", ModelToInsert.ModelClass);
                        ExecuteCommand(dConn, dTrans, "INSERT INTO Summary ([Name],[Value]) VALUES (:0,:1)", ref Current, "FullClass", ModelToInsert.FullClass);



                        for (int a = 0; a < ModelToInsert.Types.Count; a++)
                            ExecuteCommand(dConn, dTrans, "INSERT INTO CIM_Types ([ID],[Name],[Count]) VALUES (:0,:1,:2)", ref Current, a, ModelToInsert.Types[a], ElementsByType[(byte)a].Count);

                        for (int a = 0; a < ModelToInsert.Attributes.Count; a++)
                            ExecuteCommand(dConn, dTrans, "INSERT INTO CIM_Attributes ([ID],[Name]) VALUES (:0,:1)", ref Current, a, ModelToInsert.Attributes[a]);

                        for (int a = 0; a < ModelToInsert.TypesByAttribute.Count; a++)
                            ExecuteCommand(dConn, dTrans, "INSERT INTO CIM_TypesByAttribute ([ID],[Attributes]) VALUES (:0,:1)", ref Current, a, ModelToInsert.TypesByAttribute[a]);

                        for (int a = 0; a < ModelToInsert.AttributesByType.Count; a++)
                            ExecuteCommand(dConn, dTrans, "INSERT INTO CIM_AttributesByType ([ID],[Types]) VALUES (:0,:1)", ref Current, a, ModelToInsert.AttributesByType[a]);                            

                        if (ModelToInsert is CIM_DirectModel)
                        {
                            foreach (KeyValuePair<byte, List<byte>> kvp in (ModelToInsert as CIM_DirectModel).TypeFromTypes)
                            {
                                List<Byte> InBytes;
                                if (!TypeToTypes.TryGetValue(kvp.Key, out InBytes))
                                    InBytes = new List<byte>(0);
                                ExecuteCommand(dConn, dTrans, "INSERT INTO CIM_TypeLinkages ([ID], [InLinks], [OutLinks]) VALUES (:0, :1, :2)", ref Current, kvp.Key, kvp.Value, InBytes);
                            }
                            dTrans.Commit();
                        }
                    }
                    //Write our TEIDs
                    using (DbTransaction dTrans = dConn.BeginTransaction())
                    {
                        foreach (KeyValuePair<UInt64, CIM_DirectElement> kvp in TEIDs)
                            ExecuteCommand(dConn, dTrans, "INSERT INTO CIM_TEIDs ([TEID], [rdfID], [ID]) VALUES (:0, :1, :2)", ref Current, kvp.Key, kvp.Value.rdfID, kvp.Value.ID);
                        dTrans.Commit();
                    }

                    //Write our elements
                    using (DbTransaction dTrans = dConn.BeginTransaction())
                    {
                        foreach (CIM_DirectElement Elem in Elements.Values)
                            ExecuteCommand(dConn, dTrans, "INSERT INTO CIM_Elements ([ID], [rdfID], [Type], [Start], [Length], [InLinks], [OutLinks], [Checksum]) VALUES (:0, :1, :2, :3, :4, :5, :6, :7)", ref Current, Elem.ID, Elem.rdfID, Elem.Type, Elem.Start, Elem.Length, Elem.InLinks, Elem.OutLinks, Elem.CheckSum);
                        dTrans.Commit();
                    }

                    //Write our traces
                    using (DbTransaction dTrans = dConn.BeginTransaction())
                    {
                        foreach (CIM_Trace Trace in ModelToInsert.Traces.Values)
                        {
                            ExecuteCommand(dConn, dTrans, "INSERT INTO CIM_TRACES ([ID], [Name], [Description], [rdfID], [Elements],[Groups],[Radial]) VALUES (:0, :1, :2,:4, :5, :6)", ref Current, Trace.ID, Trace.Name, Trace.Description, Trace.rdfID, Trace.Elements, Trace.Groups, Trace.Radial);
                            float temp=0;
                            foreach (CIM_Element Elem in Trace.Elements)
                                ExecuteCommand(dConn, dTrans, "INSERT INTO CIM_Trace_Elements ([TraceID], [rdfID], [TEID]) VALUES (:0,:1,:2)", ref temp, Trace.ID, Elem.rdfID, Convert.ToUInt64(Elem.TEID));
                        }
                        dTrans.Commit();
                    }                   
 
                    //Write out our reserved names
                    using (DbTransaction dTrans = dConn.BeginTransaction())
                    {
                        foreach (String ReservedName in ModelToInsert.ReservedTraceNames)
                            ExecuteCommand(dConn, dTrans, "INSERT INTO CIM_Reserved_Trace_Names ([Name]) VALUES (:0)", ref Current, ReservedName);
                        dTrans.Commit();
                    }

                    //Write our companies
                    using (DbTransaction dTrans = dConn.BeginTransaction())
                    {
                        foreach (CIM_Element Company in ModelToInsert.ElementsOfType("cim:Company"))
                            ExecuteCommand(dConn, dTrans, "INSERT INTO CIM_Companies ([ID],[TEID],[rdfID],[Name],[PrimaryPhone],[DUNS]) VALUES (:0, :1, :2, :3, :4, :5)", ref Current, Company.ID, Company.TEID, Company.rdfID, Company.Name, Company["cim:IdentifiedObject.primaryphone"], Company["etx:Company.DUNS"]);
                        dTrans.Commit();
                    }
                    dConn.Close();
                }
            }
        }

        /// <summary>
        /// Create tables for the database
        /// </summary>
        /// <param name="dConn"></param>
        private void CreateTables(DbConnection dConn)
        {
            float Current = 0f;
            ExecuteCommand(dConn, null, "CREATE TABLE Summary ([Name] VARCHAR PRIMARY KEY, [Value] VARCHAR)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_Types ([ID] INTEGER PRIMARY KEY, [Name] VARCHAR UNIQUE, [Count] INTEGER)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_Attributes ([ID] INTEGER PRIMARY KEY, [Name] VARCHAR UNIQUE)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_TypesByAttribute ([ID] INTEGER PRIMARY KEY, [Attributes] BLOB)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_AttributesByType ([ID] INTEGER PRIMARY KEY, [Types] BLOB)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_TEIDS ([TEID] INTEGER PRIMARY KEY, [ID] INTEGER UNIQUE, [rdfID] BLOB UNIQUE)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_Elements ([ID] INTEGER PRIMARY KEY, [rdfID] BLOB UNIQUE, [TYPE] BYTE, [START] BIGINT, [LENGTH] INT, [Name] VARCHAR, [InLinks] BLOB, [OutLinks] BLOB, [Checksum] BLOB)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_InternalElements ([ID] INTEGER PRIMARY KEY, [CIMData] BLOB)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_TypeLinkages ([ID] INTEGER PRIMARY KEY, [InLinks] BLOB, [OutLinks] BLOB)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_Traces ([ID] INTEGER PRIMARY KEY, [Name] VARCHAR UNIQUE, [Description] VARCHAR, [rdfID] VARCHAR, [Elements] BLOB, [Groups] VARCHAR, [Radial] INTEGER)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_Companies ([ID] INTEGER PRIMARY KEY, [TEID] INTEGER, [rdfID] BLOB UNIQUE, [Name] VARCHAR, [PrimaryPhone] VARCHAR, [DUNS] VARCHAR)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_Trace_Elements ([TraceID] INTEGER, [rdfID] BLOB, [TEID] INTEGER)", ref Current);
            ExecuteCommand(dConn, null, "CREATE TABLE CIM_Reserved_Trace_Names ([Name] VARCHAR PRIMARY KEY)", ref Current);            
        }

        /// <summary>
        /// Execute a command against the database
        /// </summary>
        /// <param name="dConn"></param>
        /// <param name="dTrans"></param>
        /// <param name="CommandText"></param>
        /// <param name="inValues"></param>
        /// <param name="Current"></param>
        /// <returns></returns>        
        public Int64 ExecuteCommand(DbConnection dConn, DbTransaction dTrans, String CommandText, ref float Current, params object[] inValues)
        {
            using (DbCommand dCmd = dConn.CreateCommand())
            {
                dCmd.CommandText = CommandText + "; SELECT last_insert_rowid() AS [ID]";
                dCmd.Transaction = dTrans;
                for (int a = 0; a < inValues.Length; a++)
                    if (inValues[a] is List<byte>)
                        AddWithValue(dCmd, ":" + a.ToString(), (inValues[a] as List<byte>).ToArray());
                    else if (inValues[a] is byte[])
                        AddWithValue(dCmd, ":" + a.ToString(), (byte[])inValues[a]);
                    else if (inValues[a] is IEnumerable<UInt16>)
                    {
                        IEnumerable<UInt16> OutShort = inValues[a] as IEnumerable<UInt16>;

                        List<byte> outBytes = new List<byte>();
                        foreach (UInt16 ThisShort in OutShort)
                            outBytes.AddRange(BitConverter.GetBytes(ThisShort));
                        AddWithValue(dCmd, ":" + a.ToString(), outBytes.ToArray());
                    }
                    else if (inValues[a] is CIM_RdfID)
                    {
                        byte[] inBytes = (inValues[a] as CIM_RdfID).GetBytes();
                        AddWithValue(dCmd, ":" + a.ToString(), inBytes);
                    }
                    else if (inValues[a] is IEnumerable<CIM_DirectElement>)
                    {
                        using (MemoryStream OutStream = new MemoryStream())
                        using (BinaryWriter bW = new BinaryWriter(OutStream))
                        {
                            foreach (CIM_DirectElement Elem in (inValues[a] as IEnumerable<CIM_DirectElement>))
                            {
                                bW.Write(Elem.Type);
                                bW.Write(Elem.ID);
                            }
                            bW.Flush();
                            AddWithValue(dCmd, ":" + a.ToString(), OutStream.ToArray());
                        }
                    }
                    else if (inValues[a] is IEnumerable<CIM_DirectLink>)
                    {
                        using (MemoryStream OutStream = new MemoryStream())
                        using (BinaryWriter bW = new BinaryWriter(OutStream))
                        {
                            foreach (CIM_DirectLink Elem in (inValues[a] as IEnumerable<CIM_DirectLink>))
                            {
                                bW.Write(Elem.TypeByte);
                                bW.Write(Elem.TargetID);
                            }
                            bW.Flush();
                            AddWithValue(dCmd, ":" + a.ToString(), OutStream.ToArray());
                        }
                    }

                    else if (inValues[a] is CIM_RdfID[])
                    {
                        List<Byte> outBytes = new List<byte>();
                        foreach (CIM_RdfID rdfID in inValues[a] as CIM_RdfID[])
                            outBytes.AddRange(rdfID.GetBytes());
                        AddWithValue(dCmd, ":" + a.ToString(), outBytes.ToArray());
                    }
                    else if (inValues[a] is Array)
                        Console.WriteLine();
                    else
                        AddWithValue(dCmd, ":" + a.ToString(), inValues[a]);
                Current++;
                return Convert.ToInt64(dCmd.ExecuteScalar());
            }
        }

        /// <summary>
        /// Add a parameter to the database
        /// </summary>
        /// <param name="dCmd"></param>
        /// <param name="Title"></param>
        /// <param name="Value"></param>
        private void AddWithValue(DbCommand dCmd, String Title, Object Value)
        {
            DbParameter dParam = dCmd.CreateParameter();
            dParam.ParameterName = Title;
            dParam.Value = Value;
            dCmd.Parameters.Add(dParam);
        }
        #endregion

        /// <summary>
        /// Return our checksums for our database
        /// </summary>
        /// <returns></returns>
        public override Dictionary<CIM_RdfID, byte[]> GetElementChecksums()
        {
            Dictionary<CIM_RdfID,  byte[]> ElemInfo = new Dictionary<CIM_RdfID, byte[]>();            
            foreach (KeyValuePair<CIM_RdfID, CIM_Element> Elem in this.Elements)
                ElemInfo.Add(Elem.Key, Elem.Value.CheckSum);                    
            return ElemInfo;
        }
    }
}